using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using System.Reflection;
using System.ComponentModel;
using System.Collections;

/// <summary>
/// A sortable and bindable collection (view) class
/// </summary>
public class SortedView : IBindingList, IList
{
    #region ListItem class
    private class ListItem : IComparable
    {
        public object Key;
        public object Item;

        public ListItem(Object key, Object item)
        {
            this.Key = key;
            this.Item = item;
        }

        public int CompareTo(Object obj)
        {
            Object target = ((ListItem)obj).Key;
            if (Key is IComparable)
            {
                return ((IComparable)Key).CompareTo(target);
            }
            else
            {
                if (Key != null)
                {
                    if (Key.Equals(target))
                        return 0;
                    else
                        return Key.ToString().CompareTo(target.ToString());
                }
                else
                    return -1;
            }

        }
        public override string ToString()
        {
            return Key.ToString();
        }

    }
    #endregion

    #region sorted enumerator

    private class SortedEnumerator : IEnumerator
    {
        private ArrayList mSortIndex;
        private ListSortDirection mSortOrder;
        private int index;

        public SortedEnumerator(ArrayList sortIndex, ListSortDirection direction)
        {
            mSortIndex = sortIndex;
            mSortOrder = direction;
            Reset();
        }

        public object Current
        {
            get { return ((ListItem)mSortIndex[index]).Item; }
        }

        public bool MoveNext()
        {
            if (mSortOrder == ListSortDirection.Ascending)
            {
                if (index < mSortIndex.Count - 1)
                {
                    index++;
                    return true;
                }
                else
                    return false;
            }
            else
            {
                if (index > 0)
                {
                    index--;
                    return true;
                }
                else
                    return false;
            }
        }

        public void Reset()
        {
            if (mSortOrder == ListSortDirection.Ascending)
                index = -1;
            else
                index = mSortIndex.Count;
        }


    }
    #endregion

    private IList mList;
    private bool mSupportsBinding;
    private IBindingList mBindingList;
    private bool mSorted = false;
    private PropertyDescriptor mSortBy;
    private ListSortDirection mSortOrder = ListSortDirection.Ascending;
    private ArrayList mSortIndex = new ArrayList();

    /// <summary>
    /// Creates a new view based on the provided IList object.
    /// </summary>
    /// <param name="list">The IList (collection) containing the data.</param>
    public SortedView(IList list)
    {
        mList = list;
        if (mList is IBindingList)
        {
            mSupportsBinding = true;
            mBindingList = (IBindingList)mList;
            mBindingList.ListChanged += SourceChanged;
        }
    }

    private void SourceChanged(object sender, ListChangedEventArgs e)
    {
        if (mSorted)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                if (mSortOrder == ListSortDirection.Ascending)
                    mSortIndex.Add(new ListItem(mSortBy.GetValue(mList[e.NewIndex]), mList[e.NewIndex]));
                else
                    mSortIndex.Insert(0, new ListItem(mSortBy.GetValue(mList[e.NewIndex]), mList[e.NewIndex]));


                if (ListChanged != null)
                    ListChanged(this, new ListChangedEventArgs(ListChangedType.ItemAdded, mSortIndex.Count - 1));

            }
            else
            {
                DoSort();
            }
        }
        else
        {
            if (ListChanged != null)
                ListChanged(this, e);
        }
    }
    #region Sort Unsort

    private void DoSort()
    {
        if (mSortBy == null)
        {
            foreach (Object obj in mList)
            {
                mSortIndex.Add(new ListItem(obj, obj));
            }
        }
        else
        {
            foreach (Object obj in mList)
            {
                mSortIndex.Add(new ListItem(mSortBy.GetValue(obj), obj));
            }
        }
        mSortIndex.Sort();
        mSorted = true;

        if (ListChanged != null)
            ListChanged(this, new ListChangedEventArgs(ListChangedType.Reset, 0));
    }

    private void UndoSort()
    {
        mSortIndex.Clear();
        mSortBy = null;
        mSortOrder = ListSortDirection.Ascending;
        mSorted = false;

        if (ListChanged != null)
            ListChanged(this, new ListChangedEventArgs(ListChangedType.Reset, 0));
    }

    #endregion

    #region IList

    /// <summary>
    /// Copies the underlying data to an array.
    /// </summary>
    /// <param name="array">Array to hold the data.</param>
    /// <param name="index">Index in the array to start the copy.</param>
    public void CopyTo(System.Array array, int index)
    {
        mList.CopyTo(array, index);
    }

    /// <summary>
    /// Returns the number of items in the collection.
    /// </summary>
    public int Count
    {
        get { return mList.Count; }
    }

    /// <summary>
    /// Returns True if the underlying IList object is synchronized.
    /// </summary>
    public bool IsSynchronized
    {
        get { return mList.IsSynchronized; }
    }

    /// <summary>
    /// Returns the SyncRoot object for the underling IList object.
    /// </summary>
    public object SyncRoot
    {
        get { return mList.SyncRoot; }
    }

    /// <summary>
    /// Returns the enumerator for the list.
    /// </summary>
    /// <remarks>
    /// If the view is sorted, the enumerator provides a sorted
    /// view of the data. If the view is not sorted, the enumerator
    /// from the underlying IList object is used.
    /// </remarks>
    public System.Collections.IEnumerator GetEnumerator()
    {
        if (IsSorted)
            return new SortedEnumerator(mSortIndex, mSortOrder);
        else
            return mList.GetEnumerator();
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public int Add(Object value)
    {
        return mList.Add(value);
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public void Clear()
    {
        mList.Clear();
    }

    /// <summary>
    /// Returns True if the list contains the specified value.
    /// </summary>
    public bool Contains(Object value)
    {
        return mList.Contains(value);
    }

    /// <summary>
    /// Returns the index of the specified value.
    /// </summary>
    /// <remarks>
    /// If the view is sorted then the index is the index 
    /// within the sorted list, not the underlying IList object.
    /// </remarks>
    public int IndexOf(Object value)
    {
        if (mSorted)
        {
            int index = 0;
            foreach (ListItem item in mSortIndex)
            {
                if (item.Item.Equals(value))
                    return index;
                index++;
            }
            return -1;
        }
        else
        {
            return mList.IndexOf(value);
        }
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public void Insert(int index, Object value)
    {
        mList.Insert(index, value);
    }

    /// <summary>
    /// Returns True if the underling IList object is fixed size.
    /// </summary>
    public bool IsFixedSize
    {
        get { return mList.IsFixedSize; }
    }

    /// <summary>
    /// Returns True. The view is always read only.
    /// </summary>
    public bool IsReadOnly
    {
        get { return true; }
    }

    /// <summary>
    /// Returns the item at the specified index. Does NOT allow setting a new
    /// value.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If the list is sorted, this returns the items in the appropriate sorted
    /// order. Otherwise the order will match that of the underlying IList object.
    /// </para><para>
    /// Attempting to set the value will result in an exception. To set a value,
    /// set it in the underling IList object.
    /// </para>
    /// </remarks>
    /// <param name="index">The index of the item to return.</param>
    /// <returns>The item at the specified index.</returns>
    public Object this[int index]
    {
        get
        {
            if (mSorted)
                return GetSortedItem(index);
            else
                return mList[index];
        }
        set
        {
            if (mSorted)
            {
                int pos = mList.IndexOf(GetSortedItem(index));
                mList[pos] = value;
                if (!mSupportsBinding)
                    DoSort();
            }
            else
            {
                mList[index] = value;
            }
        }

    }

    /// <summary>
    /// Returns the appropriate item for an index
    /// based on the current sort.
    /// </summary>
    private Object GetSortedItem(int index)
    {
        if (mSortOrder == ListSortDirection.Ascending)
            return ((ListItem)mSortIndex[index]).Item;
        else
            return ((ListItem)mSortIndex[mSortIndex.Count - 1 - index]).Item;
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public void Remove(Object value)
    {
        mList.Remove(value);
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public void RemoveAt(int index)
    {
        if (mSorted)
        {
            int pos = mList.IndexOf(GetSortedItem(index));
            mList.RemoveAt(pos);
        }
        else
            mList.RemoveAt(index);
    }
    #endregion

    #region IBindingList
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public void AddIndex(PropertyDescriptor property)
    {
        if (mSupportsBinding)
            mBindingList.AddIndex(property);
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public Object AddNew()
    {
        if (mSupportsBinding)
        {
            return mBindingList.AddNew();
        }
        else
            return null;
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool AllowEdit
    {
        get
        {
            if (mSupportsBinding)
                return mBindingList.AllowEdit;
            else
                return false;
        }

    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool AllowNew
    {
        get
        {
            if (mSupportsBinding)
                return mBindingList.AllowNew;
            else
                return false;
        }
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool AllowRemove
    {
        get
        {
            if (mSupportsBinding)
                return mBindingList.AllowRemove;
            else
                return false;
        }
    }

    /// <summary>
    /// Applies a sort to the view.
    /// </summary>
    /// <param name="property">The text name of the property on which to sort.</param>
    /// <param name="direction">The direction to sort the data.</param>
    public void ApplySort(string property, ListSortDirection direction)
    {
        Type itemType = null;
        if (property.Length > 0)
        {
            Type t = ((Object)mList).GetType();
            MemberInfo[] defs = t.GetDefaultMembers();
            foreach (MemberInfo member in defs)
            {
                if (member.MemberType == MemberTypes.Property)
                {
                    itemType = ((PropertyInfo)member).GetGetMethod().ReturnType;
                    break;
                }
            }
        }
        if (itemType == null)
        {
            if (mList.Count > 0)
                itemType = mList[0].GetType();
            else
                throw new Exception("can not determine collection type");
        }

        PropertyDescriptorCollection props;
        props = TypeDescriptor.GetProperties(itemType);
        foreach (PropertyDescriptor prop in props)
        {
            if (prop.Name == property)
            {
                mSortBy = prop;
                break;
            }
        }

        ApplySort(mSortBy, direction);
    }

    /// <summary>
    /// Applies a sort to the view.
    /// </summary>
    /// <param name="property">A PropertyDescriptor for the property on which to sort.</param>
    /// <param name="direction">The direction to sort the data.</param>
    public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
    {
        mSortBy = property;
        mSortOrder = direction;
        DoSort();
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public int Find(PropertyDescriptor property, object key)
    {
        if (mSupportsBinding)
            return mBindingList.Find(property, key);
        else
            return -1;
    }

    /// <summary>
    /// Returns True if the view is currently sorted.
    /// </summary>
    public bool IsSorted
    {
        get
        {
            return mSorted;
        }
    }

    /// <summary>
    /// Raised to indicate that the list's data has changed.
    /// </summary>
    /// <remarks>
    /// This event is raised if the underling IList object's data changes
    /// (assuming the underling IList also implements the IBindingList
    /// interface). It is also raised if the sort property or direction
    /// is changed to indicate that the view's data has changed.
    /// </remarks>
    public event ListChangedEventHandler ListChanged;

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public void RemoveIndex(PropertyDescriptor property)
    {
        if (mSupportsBinding)
            mBindingList.RemoveIndex(property);
    }

    /// <summary>
    /// Removes any sort currently applied to the view.
    /// </summary>
    public void RemoveSort()
    {
        UndoSort();
    }

    /// <summary>
    /// Returns the direction of the current sort.
    /// </summary>
    public ListSortDirection SortDirection
    {
        get { return mSortOrder; }
    }

    /// <summary>
    /// Returns the PropertyDescriptor of the current sort.
    /// </summary>
    public PropertyDescriptor SortProperty
    {
        get { return mSortBy; }
    }

    /// <summary>
    /// Returns True since this object does raise the
    /// ListChanged event.
    /// </summary>
    public bool SupportsChangeNotification
    {
        get { return true; }
    }

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool SupportsSearching
    {
        get
        {
            if (mSupportsBinding)
                return mBindingList.SupportsSearching;
            else
                return false;
        }
    }

    /// <summary>
    /// Returns True. Sorting is supported.
    /// </summary>
    public bool SupportsSorting
    {
        get { return true; }
    }

    #endregion
}

